// ----------------------------------------------------------------------------
// allocator.h
// ----------------------------------------------------------------------------
/*
 Description:
 メモリブロック確保用ユーティリティ. 
 ----------------------------------------------------------------
 使い方.
 
 1. メモリアロケータによるメモリ確保を行う前に、初期化関数を呼び出す.
	登録オブジェクト(registration_object)を使用しない場合、明示的に初期化処理を呼び出す必要がある.
 
    fw::MemorySubsystem::Initialize();
 
 
	アプリケーションの終了時も同様に終端処理を呼び出す.
 
	fw::MemorySubsystem::Terminate();
 
	※基本的にruntimeより上位のライブラリは登録オブジェクトを使用するので、初期化の必要はない.
	まったくallocator.cppを使用しない場合、メモリアロケータの初期化は自動的にスキップされる. 
	
 
 2. メモリ確保/開放を行う場合、FW_MALLOC(FW_MEMALIGN), FW_FREEを使用する. 
 
    void* memBlock = FW_MALLOC(sizeof(char) * 1024);
	...
	FW_FREE(memBlock);

 */
#pragma once
#include <fw/base/core/macros.h>
#include <fw/base/core/noncopyable.h>
#include <fw/base/runtime/singleton.h>

// メモリ確保/開放の追跡記録を行う.
// これを行った場合、メモリシステムにオーバーヘッドが生じる.
#ifdef FW_DEBUG
#define FW_MEMORY_TRACKING		(1)
#endif // FW_DEBUG

namespace fw
{
	// デバッグ用メモリアロケーション監視クラス.
	class MemoryTrackerImpl;
	class MemoryTracker : private NonCopyable
	{
	private:
		const char* m_trackerName;
		MemoryTrackerImpl* m_impl;
	public:
		 MemoryTracker(const char* trackerName="Default");
		~MemoryTracker();
		
		void RecordAlloc	(const void* p, size_t size, const char* file=0, int line=0);
		void RecordDealloc	(const void* p);
	}; // class MemoryTracker 
	
	// メモリアロケータ基底クラス.
	class Allocator : private NonCopyable
	{
	public:
		
		static const size_t		kAlignment_DefaultSize = 4;
		
		         Allocator() {}
		virtual ~Allocator() {}
		
		virtual void*	Allocate			(size_t size, size_t alignment=kAlignment_DefaultSize)=0;

		virtual void 	Deallocate			(void* memoryBlock)=0;
		
		virtual size_t	TotalAllocatedSize	() const=0;
		
		virtual void	RecordAlloc			(const void* p, size_t size, const char* file=0, int line=0) {}
		
		virtual void	RecordDealloc		(const void* p) {}
		
	}; // class GeneralAllocator
	
	// メモリアドレス操作用のユーティリティ. 
	namespace Memory
	{
		FW_FORCE_INLINE void* AlignForward		(void* p,		size_t align);
		FW_FORCE_INLINE void* PointerAdd		(void* p,		size_t bytes);
		FW_FORCE_INLINE const void* PointerAdd	(const void* p, size_t bytes);
		FW_FORCE_INLINE void* PointerSub		(void* p,		size_t bytes);
		FW_FORCE_INLINE const void* PointerSub	(const void* p,	size_t bytes);
	} // namespace Memory
	
	FW_FORCE_INLINE void* Memory::AlignForward(void* p, size_t align)
	{
		uintptr_t pi = uintptr_t(p);
		const size_t mod = pi % align;
		if(mod)
		{
			pi += (align - mod);
		}
		return reinterpret_cast<void*>(pi);
	}

	FW_FORCE_INLINE void* Memory::PointerAdd(void* p, size_t bytes)
	{
		return reinterpret_cast<void*>(reinterpret_cast<char*>(p) + bytes);
	}

	FW_FORCE_INLINE const void* Memory::PointerAdd(const void* p, size_t bytes)
	{
		return reinterpret_cast<const void*>(reinterpret_cast<const char*>(p) + bytes);
	}

	FW_FORCE_INLINE void* Memory::PointerSub(void* p, size_t bytes)
	{
		return reinterpret_cast<void*>(reinterpret_cast<char*>(p) - bytes);
	}
	
	FW_FORCE_INLINE const void* Memory::PointerSub(const void* p, size_t bytes)
	{
		return reinterpret_cast<const void*>(reinterpret_cast<const char*>(p) - bytes);
	}
	
	namespace MemorySubsystem
	{	// runtimeモジュールを使用する限りにおいて、初期化処理/終了処理を明示的に呼び出す必要はない.
		static const size_t kScratchBufferSize = 8*1024*1024;
		void Initialize(size_t scratchBufferSize = kScratchBufferSize);
		
		void Terminate ();
		
		Allocator& DefaultAllocator(); // 標準的なアロケータ/スレッドセーフ/メモリトラッキングあり.
		
		Allocator& ScratchAllocator(); // 一時的なメモリ用のアロケータ/スレッドセーフ/メモリトラッキングなし. 
		
	} // namespace MemorySubsystem
	
} // namespace fw

#define FW_DEFAULT_ALLOCATOR()				fw::MemorySubsystem::DefaultAllocator()
#define FW_DEFAULT_ALIGN					fw::Allocator::kAlignment_DefaultSize

FW_FORCE_INLINE void* FW_MALLOC_PROXY(fw::Allocator& alloc, size_t s, size_t a, const char* file, int line)
{
	void* p = alloc.Allocate(s, a);
#if defined(FW_MEMORY_TRACKING)
	alloc.RecordAlloc(p, s, file, line);
#endif // FW_MEMORY_TRACKING
	return p;
}

FW_FORCE_INLINE void FW_FREE_PROXY(fw::Allocator& alloc, void* p)
{
#if defined(FW_MEMORY_TRACKING)
	alloc.RecordDealloc(p);
#endif // FW_MEMORY_TRACKING
	alloc.Deallocate(p);
}

#define FW_MALLOC(s)				(FW_MALLOC_PROXY(FW_DEFAULT_ALLOCATOR(), s, FW_DEFAULT_ALIGN, __FILE__, __LINE__))
#define FW_MEMALIGN(s,a)			(FW_MALLOC_PROXY(FW_DEFAULT_ALLOCATOR(), s, a, __FILE__, __LINE__))
#define FW_FREE(p)					(FW_FREE_PROXY  (FW_DEFAULT_ALLOCATOR(), p))
#define FW_NEW(T,...)				(new (FW_MALLOC_PROXY(FW_DEFAULT_ALLOCATOR(), sizeof(T), FW_ALIGNOF(T), __FILE__, __LINE__)) T(__VA_ARGS__))
#define FW_DELETE(T,p)				do{if(p){(p)->~T();} FW_FREE_PROXY(FW_DEFAULT_ALLOCATOR(), p);}while(0)

#define FW_MALLOC_ALLOCATOR(alloc,s)		(FW_MALLOC_PROXY(alloc, s, FW_DEFAULT_ALIGN, __FILE__, __LINE__))
#define FW_MEMALIGN_ALLOCATOR(alloc,s,a)	(FW_MALLOC_PROXY(alloc, s, a, __FILE__, __LINE__))
#define FW_FREE_ALLOCATOR(alloc,p)			(FW_FREE_PROXY  (alloc, p))
#define FW_NEW_ALLOCATOR(alloc,T,...)		(new (FW_MALLOC_PROXY(alloc, sizeof(T), FW_ALIGNOF(T), __FILE__, __LINE__)) T(__VA_ARGS__))
#define FW_DELETE_ALLOCATOR(alloc,T,p)		do{if(p){(p)->~T();} FW_FREE_PROXY(alloc, p);}while(0)


